using System;
using System.Collections.Generic;

namespace Framework
{
    public class BTreeBuilder<T>
    {
        private T _context;
        private Behavior<T> _currentNode;
        private Stack<Behavior<T>> _parentNodeStack = new Stack<Behavior<T>>();

        public BTreeBuilder(T context)
        {
            _context = context;
        }

        public static BTreeBuilder<S> Begin<S>(S context)
        {
            return new BTreeBuilder<S>(context);
        }

        public BTreeBuilder<T> Action(Func<T, TaskStatus> action)
        {
            return SetChildOnParent(new ExecuteAction<T>(action));
        }

        public BTreeBuilder<T> ActionR(Func<T, TaskStatus> action)
        {
            Inverter();
            return Action(action);
        }

        public BTreeBuilder<T> Actionable(IBTreeActionable<T> actionable)
        {
            return SetChildOnParent(new ExecuteActionable<T>(actionable));
        }

        public BTreeBuilder<T> LogAction(string text)
        {
            return SetChildOnParent(new LogAction<T>(text));
        }

        public BTreeBuilder<T> WaitAction(float waitTime)
        {
            return SetChildOnParent(new WaitAction<T>(waitTime));
        }

        public BTreeBuilder<T> SubTree(BehaviorTree<T> subTree)
        {
            return SetChildOnParent(new ExecuteTree<T>(subTree));
        }

        public BTreeBuilder<T> ConditionalDecorator(Func<T, TaskStatus> conditional, bool shouldReEvaluate = true)
        {
            return PushParentNode(new ConditionalDecorator<T>(conditional, shouldReEvaluate));
        }
        
        public BTreeBuilder<T> ConditionalDecoratorR(Func<T, TaskStatus> conditional, bool shouldReEvaluate = true)
        {
            Inverter();
            return ConditionalDecorator(conditional, shouldReEvaluate);
        }

        public BTreeBuilder<T> AlwaysFail()
        {
            return PushParentNode(new AlwaysFail<T>());
        }

        public BTreeBuilder<T> AlwaysSucceed()
        {
            return PushParentNode(new AlwaysSucceed<T>());
        }

        public BTreeBuilder<T> Inverter()
        {
            return PushParentNode(new Inverter<T>());
        }

        public BTreeBuilder<T> Repeater(int repeatCount)
        {
            return PushParentNode(new Repeater<T>(repeatCount));
        }

        public BTreeBuilder<T> UntilFail()
        {
            return PushParentNode(new UntilFail<T>());
        }

        public BTreeBuilder<T> UntilSuccess()
        {
            return PushParentNode(new UntilSuccess<T>());
        }

        public BTreeBuilder<T> Parallel()
        {
            return PushParentNode(new Parallel<T>());
        }

        public BTreeBuilder<T> ParallelSelector()
        {
            return PushParentNode(new ParallelSelector<T>());
        }

        public BTreeBuilder<T> Selector()
        {
            return PushParentNode(new Selector<T>());
        }
        
        public BTreeBuilder<T> RandomSelector()
        {
            return PushParentNode(new RandomSelector<T>());
        }
        
        public BTreeBuilder<T> Sequence()
        {
            return PushParentNode(new Sequence<T>());
        }
        
        public BTreeBuilder<T> RandomSequence()
        {
            return PushParentNode(new RandomSequence<T>());
        }

        public BTreeBuilder<T> BeginCustomComposite(Composite<T> composite)
        {
            return PushParentNode(composite);
        }

        public BTreeBuilder<T> EndComposite()
        {
            _currentNode = _parentNodeStack.Pop();
            return this;
        }

        public BehaviorTree<T> Build(int updatePeriod = 1)
        {
            return new BehaviorTree<T>(_context, _currentNode, updatePeriod);
        }
        
        private BTreeBuilder<T> SetChildOnParent(Behavior<T> child)
        {
            Behavior<T> parent = _parentNodeStack.Peek();
            if (parent is Composite<T>)
            {
                (parent as Composite<T>).AddChild(child);
            }
            else if (parent is Decorator<T>)
            {
                (parent as Decorator<T>).child = child;
                EndDecorator();
            }
            return this;
        }

        private BTreeBuilder<T> EndDecorator()
        {
            _currentNode = _parentNodeStack.Pop();
            return this;
        }

        private BTreeBuilder<T> PushParentNode(Behavior<T> composite)
        {
            if (_parentNodeStack.Count > 0)
            {
                SetChildOnParent(composite);
            }
            _parentNodeStack.Push(composite);
            return this;
        }
    }
}